
/*    This file is part of the managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.frontend.korona;

// import util classes
import java.util.LinkedList;
import java.util.Iterator;

import org.managarm.jmanacclib.core.CclibUnit;
import org.managarm.jmanacclib.core.CclibException;
import org.managarm.jmanavalib.core.VaClsInfo;
import org.managarm.jmanavalib.core.VaDatatype;
import org.managarm.jmanavalib.core.VaMthInfo;
import org.managarm.jmanavalib.utils.VaImage;

// import managarm virtual architecture classes

// import ast nodes
import ast.AstRoot;
import ast.AstSpace;
import ast.AstClass;
import ast.AstGeneric;
import ast.AstInstance;

// import managarm virtual architecture classes

public final class KorCompiler {
	// the ast root that contains the classes we want to compile
	private final AstRoot root;
	
	// the va image that receives the classes
	private final VaImage image;
	
	private LinkedList<KorClsCompiler> classes = new LinkedList<KorClsCompiler>();
	private LinkedList<KorClsCompiler> queued = new LinkedList<KorClsCompiler>();
	private LinkedList<KorAstNode> queuedMethods = new LinkedList<KorAstNode>();
	
	public KorCompiler(AstRoot ast, VaImage image) {
		// save the ast root and the code image
		this.root = ast;
		this.image = image;
	}
	
	public final void build() throws CclibException {
		// build and compile the root space
		buildSpace(root);
		
		// build the queued classes
		while(queued.isEmpty() == false) {
			final KorClsCompiler compiler = queued.pop();
			compileClass(compiler);
		}
		
		while(queuedMethods.isEmpty() == false) {
			// build all methods that are part of this space
			final KorAstNode node = queuedMethods.pop();
			if(node.getChild("Statement") == null)
				continue;
				
			// create a method description
			final KorAstNode clsname = node.getChild("ClassName");
			final KorClsCompiler clscmp = resolveClass(clsname);
			final VaMthInfo desc = clscmp.describeMethod(node);
			// create a method compiler for the method and compile it		
			final KorMthCompiler compiler = new KorMthCompiler(this, clscmp,
					root, clscmp.getAstNode(), node, image, desc);
			image.addMethod(desc, compiler.build());
		}
	}
	
	final KorClsCompiler lookupClass(AstClass cls) {
		final Iterator<KorClsCompiler> iter = classes.iterator();
		while(iter.hasNext()) {
			// get the next class compiler
			final KorClsCompiler cmp = iter.next();
			
			// continue if the base class differs
			// TODO: perform valid class matching
			if(cmp.getIdent().equals(cls.getPath()) == false)
				continue;
			
			return cmp;
		}
		
		return null;
	}
	
	final KorClsCompiler lookupClass(VaClsInfo cls) {
		final Iterator<KorClsCompiler> iter = classes.iterator();
		while(iter.hasNext()) {
			final KorClsCompiler cmp = iter.next();
			if(cmp.getClsInfo().compatible(cls))
				return cmp;
		}
		
		return null;
	}
	
	final KorClsCompiler mergeGeneric(KorClsCompiler cnt, AstGeneric generic,
			AstInstance inst) throws CclibException {
		// check the pattern count
		if(generic.getPatternCnt() != inst.getPatternCount())
			throw new CclibException("Compile error: "
					+ generic.getBody().getPath()
					+ " requires " + generic.getPatternCnt()
					+ " generic arguments; " + inst.getPatternCount()
					+ " generic arguments are given",
					(CclibUnit)cnt.getAstNode().getAttribute("unit"),
					inst.line());

		final VaDatatype[] pattern = new VaDatatype[inst.getPatternCount()];
		
		// resolve the patterns
		for(int i = 0; i < inst.getPatternCount(); i++) {
			// get the pattern type node
			final KorAstNode node = inst.getPattern(i);
			
			// resolve the pattern type
			pattern[i] = cnt.typeResolve(node);
		}
		
		// lookup the generic instance
		final KorClsCompiler known = lookupGeneric(generic, pattern);
		if(known != null)
			return known;
		
		// create a new ast compiler
		final KorClsCompiler compiler = new KorClsCompiler(this, root, generic, pattern);
		
		// add it to the generics list
		classes.add(compiler);
		queued.add(compiler);
		
		// return the class
		return compiler;
	}
	
	private final KorClsCompiler lookupGeneric(AstGeneric generic, VaDatatype[] patterns) throws CclibException {
		final Iterator<KorClsCompiler> iter = classes.iterator();
		while(iter.hasNext()) {
			// get the next class compiler
			final KorClsCompiler cls = iter.next();
			
			// continue if the base class differs
			// TODO: perform valid class matching
			if(cls.getAstNode().getPath() != generic.getBody().getPath())
				continue;
			
			if(patterns.length != generic.getPatternCnt())
				continue;
			
			boolean equal = true;
			
			// compare the patterns
			for(int i = 0; i < patterns.length; i++) {
				// get the pattern identifier
				final String ident = generic.getPattern(i);
				
				// continue if the patterns do not match
				if(patterns[i].compatible(cls.getPattern(ident)) == false) {
					equal = false;
					break;
				}
			}
			
			if(equal)
				return cls;
		}
		
		return null;
	}

	private final KorClsCompiler resolveClass(KorAstNode node) {
		AstSpace space = root;
		while(node != null) {
			final String identifier = node.getAttrStr("Identifier");
			final KorAstNode child = space.getSpaceEntry(identifier);
			// move to the next subsymbol
			node = node.getChild("SubSymbol");
			// process the current subsymbol
			if(node == null && child instanceof AstClass) {
				return lookupClass((AstClass)child);
			}else if(child instanceof AstSpace) {
				space = (AstSpace)child;
			}else{
				System.err.println(node.getAttrStr("Identifier")
						+ " is not a namespace");
				System.exit(1);
			}
		}
		throw new IllegalStateException(); // should never happen
	}
	
	// build a description for every class in a namespace
	private final void buildSpace(AstSpace space) throws CclibException {
		// remember to build all methods that are part of this space
		Integer mthcnt = space.getAttrInt("NumberOfMethods");
		for(int i = 0; mthcnt != null && i < mthcnt.intValue(); i++) {
			queuedMethods.add(space.getChild("Method" + i));
		}
		
		// process the child nodes
		final Iterator<KorAstNode> iter = space.childIter();
		while(iter.hasNext()) {
			// get the next node
			final KorAstNode child = iter.next();
			
			if(child instanceof AstSpace) {
				// build the subspace
				buildSpace((AstSpace)child);
			}else if(child instanceof AstClass) {
				// get the ast node of the class
				final AstClass cls = (AstClass)child;
				
				// create a class compiler for the class
				final KorClsCompiler cmp = new KorClsCompiler(this, root, cls);
				
				// add it to the classes list
				classes.add(cmp);
				
				// add it to the queue
				queued.add(cmp);
			}else if(child instanceof AstGeneric) {
				// NOTE: generics are ignored here!
			}else{
				throw new CclibException("Internal error: Space contains "
						+ child.getClass().getSimpleName() + " node");
			}
		}
	}
	
	// compile a specific class
	private final void compileClass(KorClsCompiler cls) throws CclibException {
		// attach the class to the image
		image.addClass(cls.getClassBody());
		
		// loop thorugh the methods of the class
		for(int i = 0; i < cls.getAstNode().getAttrInt("NumberOfMethods"); i++) {
			final KorAstNode node = cls.getAstNode().getChild("Method" + i);
			if(node.getChild("Statement") == null)
				continue;
			
			// create a method description
			final VaMthInfo desc = cls.describeMethod(node);	
			// create a method compiler for the method and compile it		
			final KorMthCompiler compiler = new KorMthCompiler(this, cls,
					root, cls.getAstNode(), node, image, desc);
			image.addMethod(desc, compiler.build());
		}
	}
}
